{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Document { location: [Getter/Setter] }"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "var jsdom = require('jsdom');\n",
    "const { JSDOM } = jsdom;\n",
    "const { document } = (new JSDOM('')).window;\n",
    "global.document = document;\n",
    "var d3 = require(\"d3\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# D3.js中的数据绑定与更新\n",
    "\n",
    "卡洛斯·科罗内尔(Carlos Coronel)，史蒂文·莫里斯(Steven Morris)和彼得·罗布（Peter Rob）在其名著《Database Systems: Design, Implementation, and Management》中认为：\n",
    "\n",
    ">数据是纯粹的事实。“纯粹”意味着这种事实没有经过任何处理，其意义也没有得到揭示。而信息是数据处理的结果，它揭示了数据的意义。\n",
    "\n",
    "而数据的可视化更注重人对数据内在观察的展示。而D3能将数据和图形领域联系起来。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 进入—更新—退出模式\n",
    "\n",
    "D3使用它称为“进入—更新—退出”（enter-update-exit）模式将数据和图形建立联系。进入—更新—退出模式是所有基于D3的可视化技术的基石。\n",
    "\n",
    "* **进入模式** enter函数将返回一个新的D3对象集合，这个集合包含了所有没有可视化的数据，我们可以在这个集合上级联调用相关函数，创建新的图形来表示给定的数据。这个集合的状态称为进入模式（Enter Mode）。\n",
    "* **更新模式** data函数的返回值是一个（绑定了数据的）新D3对象集合，也就是数据与可视化图形的交集中的所有元素。现在，我们就可以针对新集合调用相关函数，并对其中的元素进行更新。这个新集合所在的状态通常称为更新状态（Update Mode）。\n",
    "* **退出模式** 当我们在一个拥有数据绑定的D3对象集合上调用selection.data(data).exit函数的时候，会得到一个新的D3对象集合。这个新的集合包含原集合上没有关联任何有效数据的图形。当然，作为一个有效的D3选集对象，我们也可以在这个新的集合上级联调用相关函数以更新这些图形，或者当我们不再需要这些图形时删除它们。这个新的集合状态称为退出模式（Exit Mode）。\n",
    "\n",
    "除了上述三种模式之外，还有一种**合并模式**（merge），`merge()`函数将传递给merge函数的给定选集与调用函数的选集进行合并，并返回二者的并集作为新的选集。在进入—更新—退出（enter-update-exit）模式中，合并函数通常用于构造一个同时涉及进入模式和更新模式的选集。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 将数组绑定为数据\n",
    "\n",
    "使用JavaScript中的数组作为D3可视化中的数据是最常见的做法。例如，你有一个数组，它里面存储了许多数据元素，你希望生成一系列图形元素，其中每一个图形代表数组中的一个记录，并且当数组中的数据进行更新的时候，图形元素也发生相应的改变。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 30px;\">10</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 45px;\">15</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 90px;\">30</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 150px;\">50</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 240px;\">80</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 195px;\">65</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 165px;\">55</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 90px;\">30</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 60px;\">20</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 30px;\">10</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 24px;\">8</div>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "var data = [10, 15, 30, 50, 80, 65, 55, 30, 20, 10, 8];\n",
    "\n",
    "var bars = d3.select(\"body\").selectAll(\"div\") //宣布页面上应该有一系列div元素，已构成图形集合\n",
    "    .data(data); //将数据绑定到这些将要创建的图形元素上 update\n",
    "\n",
    "bars.enter() // Enter选出还没有可视化的数据\n",
    "    .append(\"div\") //         为选定的每一个数据创建一个div元素，并将其加入body元素中\n",
    "    .style(\"background-color\", \"#ccc\")\n",
    "    .style('text-align', 'right')\n",
    "    .style('margin', '2px')\n",
    "    .style(\"width\", function(d) {\n",
    "        return (d * 3) + \"px\";\n",
    "    })\n",
    "    .text(function(d) {\n",
    "        return d;\n",
    "    });\n",
    "\n",
    "$$.html(document.body.innerHTML);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "jupyter": {
     "outputs_hidden": true,
     "source_hidden": true
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 39px;\">13</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 54px;\">18</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 99px;\">33</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 159px;\">53</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 249px;\">83</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 204px;\">68</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 174px;\">58</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 99px;\">33</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 69px;\">23</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 39px;\">13</div><div style=\"background-color: rgb(204, 204, 204); text-align: right; margin: 2px; width: 33px;\">11</div>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data = data.map(function(i) {\n",
    "    return i + 3;\n",
    "});\n",
    "\n",
    "bars = d3.select(\"body\").selectAll(\"div\")\n",
    "    .data(data);\n",
    "\n",
    "bars.enter()\n",
    "    .append(\"div\")\n",
    "    .style(\"background-color\", \"#ccc\")\n",
    "    .style('text-align', 'right')\n",
    "    .style('margin', '2px')\n",
    "    .merge(bars) //对进入模式选中的元素（进入模式选集）和更新模式选中的元素（更新模式选集）进行合并，从而返回二者的并集\n",
    "    .style(\"width\", function(d) {\n",
    "        return (d * 3) + \"px\"; \n",
    "    })\n",
    "    .text(function(d) {\n",
    "        return d; \n",
    "    })\n",
    "\n",
    "$$.html(document.body.innerHTML);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "动态转换方法实际上有d和i两个形式参数。其中，第一个参数d代表与图形元素相关联的数据，这个我们之前已经提过了；第二个参数i是一个从0开始的当前图形元素的下标。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 将对象绑定为数组\n",
    "\n",
    "比数组更为常见的数组形式，是对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "missing ) after argument list",
     "output_type": "error",
     "traceback": [
      "evalmachine.<anonymous>:32",
      "    }",
      "    ^",
      "",
      "SyntaxError: missing ) after argument list",
      "    at new Script (vm.js:99:7)",
      "    at createScript (vm.js:249:10)",
      "    at Object.runInThisContext (vm.js:297:10)",
      "    at run ([eval]:1054:15)",
      "    at onRunRequest ([eval]:888:18)",
      "    at onMessage ([eval]:848:13)",
      "    at process.emit (events.js:315:20)",
      "    at emit (internal/child_process.js:881:12)",
      "    at processTicksAndRejections (internal/process/task_queues.js:85:21)"
     ]
    }
   ],
   "source": [
    "var dataObject = [ // <- A\n",
    "    {\n",
    "        width: 10,\n",
    "        color: 23\n",
    "    }, {\n",
    "        width: 15,\n",
    "        color: 33\n",
    "    },\n",
    "    {\n",
    "        width: 30,\n",
    "        color: 40\n",
    "    }, {\n",
    "        width: 50,\n",
    "        color: 60\n",
    "    },\n",
    "    {\n",
    "        width: 80,\n",
    "        color: 22\n",
    "    }, {\n",
    "        width: 65,\n",
    "        color: 10\n",
    "    },\n",
    "    {\n",
    "        width: 55,\n",
    "        color: 5\n",
    "    }, {\n",
    "        width: 30,\n",
    "        color: 30\n",
    "    },\n",
    "    {\n",
    "        width: 20,\n",
    "        color: 60\n",
    "    }, {\n",
    "        width: 10,\n",
    "        color: 90\n",
    "    },\n",
    "    {\n",
    "        width: 8,\n",
    "        color: 10\n",
    "    }\n",
    "];\n",
    "var colorScale = d3.scaleLinear()\n",
    "    .domain([0, 100])\n",
    "    .range([\"#add8e6\", \"blue\"]); // <- B\n",
    "function render(data) {\n",
    "    var bars = d3.select(\"body\").selectAll(\"div.h-bar\")\n",
    "        .data(dataObject); // Update\n",
    "    // Enter\n",
    "    bars.enter()\n",
    "        .append(\"div\")\n",
    "        .attr(\"class\", \"h-bar\")\n",
    "        .merge(bars) // Enter + Update\n",
    "        .style(\"width\", function(d) { // <- C\n",
    "                style(\"width\", function(d) { // <- C\n",
    "                        return (d.width * 5) + \"px\"; // <- D\n",
    "                    })\n",
    "                    .style(\"background-color\", function(d) {\n",
    "                        return colorScale(d.color); // <- E\n",
    "                    })\n",
    "                    .text(function(d) {\n",
    "                        return d.width; // <- F\n",
    "                    });\n",
    "                // Exit\n",
    "                bars.exit().remove();\n",
    "            }\n",
    "\n",
    "            function randomValue() {\n",
    "                return Math.round(Math.random() * 100);\n",
    "            }\n",
    "            style(\"width\", function(d) { // <- C\n",
    "                return (d.width * 5) + \"px\"; // <- D\n",
    "            })\n",
    "            .style(\"background-color\", function(d) {\n",
    "                return colorScale(d.color); // <- E\n",
    "            })\n",
    "            .text(function(d) {\n",
    "                return d.width; // <- F\n",
    "            });\n",
    "            // Exit\n",
    "            bars.exit().remove();\n",
    "        }\n",
    "\n",
    "    function randomValue() {\n",
    "        return Math.round(Math.random() * 100);\n",
    "    }\n",
    "\n",
    "    render(data);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 参考资料\n",
    "\n",
    "1. <https://github.com/d3/d3-selection/blob/v1.4.1/README.md#joining-data>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "JavaScript",
   "language": "javascript",
   "name": "jslab"
  },
  "language_info": {
   "file_extension": ".js",
   "mimetype": "text/javascript",
   "name": "javascript",
   "version": ""
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {
    "height": "171px",
    "width": "194px"
   },
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
